/** @file

  Copyright (C) 2022 - 2023, Phytium Technology Co., Ltd. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <Library/ArmPlatformLib.h>
#include <Library/DebugLib.h>
#include <Library/HobLib.h>
#include <Library/PcdLib.h>
#include <Library/IoLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/ArmSmcLib.h>
#include <OEMSvc.h>
#include "S3Resume.h"
#include <Library/PhytiumPowerControlLib.h>
#include <Library/ScpLib.h>
#include <Library/OemS3Resumelib.h>
//[gliu-0035]add-start
#include <Library/KlS3SuspendUnlockHddLib.h>
//[gliu-0035]add-end

// Libo: FPDT.
#include <Library/TimerLib.h>
#include <Guid/FirmwarePerformance.h>
#include <Library/PeiServicesLib.h>
#include <Ppi/ReadOnlyVariable2.h>
// Libo: FPDT.

#define GPIO_ALTERNATE_BASE 0x28180000
#define I2C1_ALTERNATE     0x200
#define I2C2_ALTERNATE     0x204
#define I2C3_ALTERNATE_SCL 0x204
#define I2C3_ALTERNATE_SDA 0x208
#define GPIO0_PORT7        0x204
#define GPIO1_PORT5        0x208
#define GPIO1_PORT6        0x208
#define GMAC0_DELAY		   0x360
#define GMAC1_DELAY		   0x37C
#define GMAC1_ALTERNATE	   0x228



/**
  Function to read from MII register (PHY Access).

  @param Addr       Phy device physical address
  @param Reg        Phy register
  @param Data       Read data
  @param MacBaseAddress   GMAC register base address

  @retval EFI_SUCCESS     Read success
**/
#if 1
EFI_STATUS
EFIAPI
PhyRead (
  IN  UINT32   Addr,
  IN  UINT32   Reg,
  OUT UINT32   *Data,
  IN  UINTN    MacBaseAddress
  )
{
  UINT32        MiiConfig;
  UINT32        Count;

  // Check it is a valid Reg
  ASSERT (Reg < 32);

  MiiConfig = ((Addr << MIIADDRSHIFT) & MII_ADDRMSK) |
              ((Reg << MIIREGSHIFT) & MII_REGMSK)|
               MII_CLKRANGE_150_250M |
               MII_BUSY;

  // write this config to register
  MmioWrite32 (MacBaseAddress + DW_EMAC_GMACGRP_GMII_ADDRESS_OFST, MiiConfig);

  // Wait for busy bit to clear
  Count = 0;
  while (Count < 10000) {
    if (!(DW_EMAC_GMACGRP_GMII_ADDRESS_GB_GET (MmioRead32 (MacBaseAddress + DW_EMAC_GMACGRP_GMII_ADDRESS_OFST)))) {
      *Data = DW_EMAC_GMACGRP_GMII_DATA_GD_GET (MmioRead32 (MacBaseAddress + DW_EMAC_GMACGRP_GMII_DATA_OFST));
      return EFI_SUCCESS;
    }
    MemoryFence ();
    Count++;
  };
  DEBUG ((DEBUG_INFO, "SNP:PHY: MDIO busy bit timeout\r\n"));
  return EFI_TIMEOUT;
}

/**
  Function to write to the MII register (PHY Access).

  @param Addr       Phy device physical address
  @param Reg        Phy register
  @param Data       Data to write
  @param MacBaseAddress GMAC register base address

  @retval EFI_SUCCESS   Write success
**/

// Function to write to the MII register (PHY Access)
EFI_STATUS
EFIAPI
PhyWrite (
  IN UINT32   Addr,
  IN UINT32   Reg,
  IN UINT32   Data,
  IN UINTN    MacBaseAddress
  )
{
  UINT32   MiiConfig;
  UINT32   Count;

  // Check it is a valid Reg
  ASSERT(Reg < 32);

  MiiConfig = ((Addr << MIIADDRSHIFT) & MII_ADDRMSK) |
              ((Reg << MIIREGSHIFT) & MII_REGMSK)|
               MII_WRITE |
               MII_CLKRANGE_150_250M |
               MII_BUSY;
  // Write the desired value to the register first
  MmioWrite32 (MacBaseAddress + DW_EMAC_GMACGRP_GMII_DATA_OFST, (Data & 0xFFFF));

  // write this config to register
  MmioWrite32 (MacBaseAddress + DW_EMAC_GMACGRP_GMII_ADDRESS_OFST, MiiConfig);

  // Wait for busy bit to clear
  Count = 0;
  while (Count < 1000) {
    if (!(DW_EMAC_GMACGRP_GMII_ADDRESS_GB_GET (MmioRead32 (MacBaseAddress + DW_EMAC_GMACGRP_GMII_ADDRESS_OFST)))) {
      return EFI_SUCCESS;
    }
    MemoryFence ();
    Count++;
  };

  return EFI_TIMEOUT;
}

static
VOID
mars_ext_read(
  IN   UINT32    Addr,
  IN   UINT32    Regnum,
  OUT  UINT32   * Data,
  IN   UINTN     MacBaseAddress)
{
  PhyWrite(Addr,0x1e,Regnum,MacBaseAddress);
  PhyRead(Addr,0x1f,Data,MacBaseAddress);
}

static
VOID
mars_ext_write(
  IN  UINT32  Addr,
  IN  UINT32  Regnum,
  IN  UINT32  Data,
  IN  UINTN   MacBaseAddress
)
{
  PhyWrite(Addr,0x1e,Regnum,MacBaseAddress);
  PhyWrite(Addr,0x1f,Data,MacBaseAddress);
}

static
VOID
MarsRxDelay(
  IN  UINT32  Addr,
  IN  UINTN  MacBaseAddress
)
{
  UINT32 DATA = 0;

  DEBUG((EFI_D_INFO,"THE phyctl address is      0x%x\n",Addr));

//Rx delay
  mars_ext_read(Addr, 0xA003,&DATA,MacBaseAddress);
  DATA |=0x1000;
  mars_ext_write(Addr,0xA003,DATA,MacBaseAddress);
}

EFI_STATUS
EFIAPI
PhyReadId (
  IN UINT32   PhyAddr,
  IN UINTN    MacBaseAddress
  )
{
  EFI_STATUS    Status;
  UINT32        PhyId1;
  UINT32        PhyId2;
  UINT32        value;

  Status = PhyRead (PhyAddr, PHY_ID1, &PhyId1, MacBaseAddress);
  if (EFI_ERROR (Status)) {
      return Status;
  }
  Status = PhyRead (PhyAddr, PHY_ID2, &PhyId2, MacBaseAddress);
  if (EFI_ERROR (Status)) {
      return Status;
  }

  if (PhyId1 == PHY_INVALID_ID || PhyId2 == PHY_INVALID_ID) {
    return EFI_NOT_FOUND;
  }

  DEBUG ((DEBUG_INFO, "SNP:PHY: Ethernet PHY detected. PHY_ID1=0x%04X, PHY_ID2=0x%04X, PHY_ADDR=0x%02X\r\n",
          PhyId1, PhyId2, PhyAddr));
  if ((PhyId1 == 0x78) && (PhyId2 == 0x2013)) {
    DEBUG((EFI_D_INFO, "Detected Mar1-s Phy Chip \n"));
    PhyWrite(PhyAddr, 0x1e, 0x27,MacBaseAddress);
    DEBUG((EFI_D_INFO,"THE MacBaseAddress is------ 0x%lx\n",MacBaseAddress));
    PhyRead(PhyAddr,  0x1f,&value,MacBaseAddress);
    value = value & (~(1 << 15));
    DEBUG((EFI_D_INFO, "sleep config:%x", value));
    PhyWrite(PhyAddr, 0x1e, 0x27,MacBaseAddress);
    PhyWrite(PhyAddr, 0x1f, value,MacBaseAddress);
    PhyWrite(PhyAddr, 0x1e, 0x27,MacBaseAddress);
    MarsRxDelay(PhyAddr,MacBaseAddress);
  }
  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
PhyDetectDevice (
  IN UINTN        MacBaseAddress
  )
{
  UINT32       PhyAddr;
  EFI_STATUS   Status;

  DEBUG ((DEBUG_INFO, "SNP:PHY: %a ()\r\n", __FUNCTION__));

  for (PhyAddr = 0; PhyAddr < 32; PhyAddr++) {
    Status = PhyReadId (PhyAddr, MacBaseAddress);
    if (EFI_ERROR(Status)) {
      continue;
    }

    return EFI_SUCCESS;
  }

  DEBUG ((DEBUG_INFO, "SNP:PHY: Fail to detect Ethernet PHY!\r\n"));
  return EFI_NOT_FOUND;

}
#endif

STATIC inline VOID GpioClrSetBits(UINTN RegOffSet, UINT32 Mask, UINT32 Value)
{
  UINT32 Tmp;

  Tmp = MmioRead32(GPIO_ALTERNATE_BASE + RegOffSet);
  Tmp &= ~(Mask);
  Tmp |= Value;
  MmioWrite32(GPIO_ALTERNATE_BASE + RegOffSet, Tmp);
}

VOID
InitializeGpio (
  VOID
  )
{
  DEBUG((EFI_D_INFO, "InitalizeGpio:\n"));
  //I2C1 Alternate
  GpioClrSetBits(I2C1_ALTERNATE, 0xff000000, 0x22000000);
  //I2C2 Alternate
  GpioClrSetBits(I2C2_ALTERNATE, 0xff0, 0x220);
  //I2C3 SCL
  GpioClrSetBits(I2C3_ALTERNATE_SCL, 0xf, 0x2);
  //I2C3 SDA
  GpioClrSetBits(I2C3_ALTERNATE_SDA, 0xf0000000, 0x20000000);
  //GMAC0 Delay Config
  GpioClrSetBits(GMAC0_DELAY, 0xff000000, 0x21000000);
  //GMAC1 Delay Config
  GpioClrSetBits(GMAC1_DELAY, 0xff000000, 0x21000000);
  //GMAC1 Alternate
  GpioClrSetBits(GMAC1_ALTERNATE, 0x0fffff00, 0x01111100);
  //Gpio0 port7  use for ec interrupt
  GpioClrSetBits(GPIO0_PORT7, 0xf0000000, 0x10000000);
  //Gpio1 port5  ust for touchpad interrupt
  GpioClrSetBits(GPIO1_PORT5, 0xf0000, 0x10000);
  //Gpio1 port6  use for power button interrupt
  GpioClrSetBits(GPIO1_PORT6, 0xf000, 0x1000);
  //set pad driver strength
  MmioWrite32(0x28180488, 0x0);
}


VOID
S3SystemOffEntry(
  UINTN pfdi_mum,
  UINTN gd_base
  )
{
  SystemOff();
  while(1);
}

VOID
S3SystemResetEntry(
  UINTN pfdi_mum,
  UINTN gd_base
  )
{
  SystemReboot();
  while(1);
}

VOID
S3SuspendStartEntry(
  UINTN pfdi_mum,
  UINTN gd_base
  )
{
  ARM_SMC_ARGS  ArmSmcArgs;
  VOID          *pDevQue;
  // Libo: FPDT.
  EFI_STATUS    Status;
  UINT32        VarAttrib = 0;
  UINTN         VarSize = 0;
  S3_PERFORMANCE_TABLE    *AcpiS3PerformanceTable = NULL;
  FIRMWARE_PERFORMANCE_VARIABLE    PerformanceVariable;
  EFI_PEI_READ_ONLY_VARIABLE2_PPI  *ReadOnlyVariable = 0;
  // Libo: FPDT.

  // Libo: FPDT.
  Status = PeiServicesLocatePpi (
              &gEfiPeiReadOnlyVariable2PpiGuid,
              0,
              NULL,
              (VOID **)&ReadOnlyVariable
              );
  if (EFI_SUCCESS == Status) {
    VarSize = sizeof (PerformanceVariable);
    Status = ReadOnlyVariable->GetVariable (
               ReadOnlyVariable,
               EFI_FIRMWARE_PERFORMANCE_VARIABLE_NAME,
               &gEfiFirmwarePerformanceGuid,
               &VarAttrib,
               &VarSize,
               &PerformanceVariable
               );
    if (EFI_SUCCESS == Status) {
      AcpiS3PerformanceTable = (S3_PERFORMANCE_TABLE *)PerformanceVariable.S3PerformanceTablePointer;
      if (AcpiS3PerformanceTable->Header.Signature == EFI_ACPI_5_0_FPDT_S3_PERFORMANCE_TABLE_SIGNATURE) {
        AcpiS3PerformanceTable->S3Suspend.SuspendStart = GetTimeInNanoSecond (GetPerformanceCounter ());
      }
    }
  };
  // Libo: FPDT.

  //[gliu-0035]add-start
  pDevQue = PreparePciScan();
  DEBUG((EFI_D_INFO,"SuspendStartEntry device queue base : %llx\n",pDevQue));
  PciScanBus(pDevQue, 0);
  DumpDeviceInfo();
  //[gliu-0035]add-end
  ArmSmcArgs.Arg0 = PFDI_DONE;
  DEBUG((EFI_D_INFO,"SuspendStartEntry pfdi_mum : %lld , gd_base : %llx \n",pfdi_mum,gd_base));
  ArmCallSmc (&ArmSmcArgs);
  while(1);
}

VOID
S3SuspendFinishEntry(
  UINTN pfdi_mum,
  UINTN gd_base
  )
{
  ARM_SMC_ARGS  ArmSmcArgs;
  ArmSmcArgs.Arg0 = PFDI_DONE;

  DEBUG((EFI_D_INFO,"S3SuspendFinishEntry pfdi_mum : %lld , gd_base : %llx \n",pfdi_mum,gd_base));

//Mars_1s_v1 Rxdelay
  PhyDetectDevice (GMACBASEADDRESS0);
  PhyDetectDevice (GMACBASEADDRESS1);

  /*MmioWrite32(0x28180488, 0x0);*/
  //GMAC Delay Config
  /*MmioWrite32(0x28180360, (MmioRead32(0x28180360) & 0xffff) | 0x21000000);*/
  /*MmioWrite32(0x2818037C, (MmioRead32(0x2818037C) & 0xffff) | 0x21000000);*/
  //GMAC1
  /*MmioWrite32(0x28180228, 0x1111100);*/
  InitializeGpio();

  //clear i2c interrupt
  DEBUG((EFI_D_INFO, "clear i2c interrupt.\n"));
  MmioRead32(0x28006000 + 0x40);
  MmioRead32(0x28007000 + 0x40);
  MmioRead32(0x28008000 + 0x40);
  MmioRead32(0x28009000 + 0x40);

  MmioWrite32(0x28006000 + 0x30, 0);
  MmioWrite32(0x28007000 + 0x30, 0);
  MmioWrite32(0x28008000 + 0x30, 0);
  MmioWrite32(0x28009000 + 0x30, 0);
  DEBUG((EFI_D_INFO, "reg:0x%x\n", MmioRead32(0x28006000 + 0x2c)));
  DEBUG((EFI_D_INFO, "reg:0x%x\n", MmioRead32(0x28007000 + 0x2c)));
  DEBUG((EFI_D_INFO, "reg:0x%x\n", MmioRead32(0x28008000 + 0x2c)));
  DEBUG((EFI_D_INFO, "reg:0x%x\n", MmioRead32(0x28009000 + 0x2c)));

#ifdef NOTEBOOK_V2
  //Hda
  InitializeHdaDxe ();
#endif

  //enable x100 read cpu temparature.
  /*func_set_i2c_port(1);*/

  ArmCallSmc (&ArmSmcArgs);
  while(1);
}

VOID
S3SuspendEndEntry(
  UINTN pfdi_mum,
  UINTN gd_base
  )
{
  ARM_SMC_ARGS  ArmSmcArgs;
  // Libo: FPDT.
  EFI_STATUS    Status;
  UINT32        VarAttrib = 0;
  UINTN         VarSize = 0;
  S3_PERFORMANCE_TABLE    *AcpiS3PerformanceTable = NULL;
  FIRMWARE_PERFORMANCE_VARIABLE    PerformanceVariable;
  EFI_PEI_READ_ONLY_VARIABLE2_PPI  *ReadOnlyVariable = 0;
  // Libo: FPDT.

  // Libo: FPDT.
  Status = PeiServicesLocatePpi (
              &gEfiPeiReadOnlyVariable2PpiGuid,
              0,
              NULL,
              (VOID **)&ReadOnlyVariable
              );
  if (EFI_SUCCESS == Status) {
    VarSize = sizeof (PerformanceVariable);
    Status = ReadOnlyVariable->GetVariable (
               ReadOnlyVariable,
               EFI_FIRMWARE_PERFORMANCE_VARIABLE_NAME,
               &gEfiFirmwarePerformanceGuid,
               &VarAttrib,
               &VarSize,
               &PerformanceVariable
               );
    if (EFI_SUCCESS == Status) {
      AcpiS3PerformanceTable = (S3_PERFORMANCE_TABLE *)PerformanceVariable.S3PerformanceTablePointer;
      if (AcpiS3PerformanceTable->Header.Signature == EFI_ACPI_5_0_FPDT_S3_PERFORMANCE_TABLE_SIGNATURE) {
        AcpiS3PerformanceTable->S3Suspend.SuspendEnd = GetTimeInNanoSecond (GetPerformanceCounter ());
      }
    }
  };
  // Libo: FPDT.

  SetMem((VOID*)&ArmSmcArgs, sizeof(ARM_SMC_ARGS), 0);

    ArmSmcArgs.Arg0 = 0xC2000F04;
    ArmSmcArgs.Arg1 = 0x1;
    ArmCallSmc (&ArmSmcArgs);

    mdelay(10);
    disable_vtt();
    mdelay(40);
    ArmSmcArgs.Arg0 = 0xC2000F04;
    ArmSmcArgs.Arg1 = 0x2;
    ArmCallSmc (&ArmSmcArgs);
    set_s3_flag();
    while(1);
}

EFI_STATUS
S3FuncRegister(
  VOID
  )
{
  ARM_SMC_ARGS  ArmSmcArgs;
  PfdiVectors PfdiV;

  PfdiV.SystemOffEntry =(UINT64)AsmS3SystemOffEntry;
  DEBUG((EFI_D_INFO,"PfdiV.SystemOffEntry : %lx\n",PfdiV.SystemOffEntry));

  PfdiV.SystemResetEntry =(UINT64)AsmS3SystemResetEntry;
  DEBUG((EFI_D_INFO,"PfdiV.SystemResetEntry : %lx\n",PfdiV.SystemResetEntry));

  PfdiV.SuspendStartEntry =(UINT64)AsmS3SuspendStartEntry;
  DEBUG((EFI_D_INFO,"PfdiV.SuspendStartEntry : %lx\n",PfdiV.SuspendStartEntry));

  PfdiV.SuspendEndEntry = (UINT64)S3SuspendEndEntry;
  DEBUG((EFI_D_INFO,"PfdiV.SuspendEndEntry : %lx\n",PfdiV.SuspendEndEntry));

  PfdiV.SuspendFinishEntry =(UINT64)AsmS3SuspendFinishEntry;
  DEBUG((EFI_D_INFO,"PfdiV.SuspendFinishEntry : %lx\n",PfdiV.SuspendFinishEntry));
  //[jckuang-0025]
  OemS3FuncRegister((volatile OEM_PFDI_VECTORS*)&PfdiV);
  //[jckuang-0025]
  ArmSmcArgs.Arg0 = PFDI_REGISTER;
  ArmSmcArgs.Arg1 = (UINTN)&PfdiV;
  ArmCallSmc (&ArmSmcArgs);

  if(ArmSmcArgs.Arg0 != 0) {
      DEBUG((EFI_D_INFO, "register pfdi fail.\n"));
      while(1);
  }

  return EFI_SUCCESS;
}





